#!/usr/bin/env python
"""Command line based FTP client"""

import sys
import os
import cmd
import re
import time
from optparse import OptionParser, OptionValueError

from ftp import *

class ParserException(Exception):
    """No connection present."""
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

class CommandNotFound(Exception):
    """No connection present."""
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

class FTPCommandLine(cmd.Cmd):
    """CommandLine class."""

    verbose = False     # toggle verbose mode

    def __init__(self, prompt="> ", ftpOptions = None, sftp=False, verbose=False):
        """CommandLine constructor.

        Arguments:
        prompt -- user prompt
        ftpOptions -- dictionary with ftp options
        """
        cmd.Cmd.__init__(self)


        self.prompt = prompt
        self.verbose = verbose

        if sftp:
            self.ftp = IrisSFTP(verbose=self.verbose)
        else:
            self.ftp = IrisFTP(verbose=self.verbose)

        if ftpOptions:
            self.ftp.host = ftpOptions['host']
            self.ftp.username = ftpOptions['username']
            self.ftp.password = ftpOptions['password']
            if self.ftp.host and self.ftp.username and self.ftp.password: 
                try:
                    self.ftp.connect()
                except ConnectionException, e:
                    print e

    def do_info(self, line):
        """Print information about current ftp session."""
        if self.ftp.connected:
            print "Connected to:"
        else:
            print "No connection active, settings stored:"
        print "\tHost: %s" % self.ftp.host
        print "\tUsername: %s" % self.ftp.username
        print "\tPassword: %s" % self.ftp.password

    def do_set(self, line):
        """Set localvalue of the shell. Syntax 'set <name> <value>'.
        """
        args = line.split()
        if args[0] in ('host', 'username', 'password'):
            if self.ftp.connected:
                print "Cannot change fpt settings while connection is active"
                print "Please disconnect before changing %s" % args[0]
            else:
                setattr( self.ftp, args[0], args[1] )
                print "%s = %s" % (args[0], args[1] )

    def do_connect(self, line):
        """Connect to the ftp server, return welcome message."""
        try:
            print self.ftp.connect()
        except ConnectionException, e:
            print e

    def do_disconnect(self, line):
        """Disconnect from ftp server."""
        self.ftp.disconnect()

    def default(self, line):
        """Function for unregocnized commands."""
        print "Command not recognized: %s" % line

    def do_help(self, args):
        """Get help on commands.

        'help' or '?' without arguments prints list of commands
        'help/? <command>' prints help on <command>
        """
        cmd.Cmd.do_help(self, args)

    def do_quit(self, line):
        """Quit IrisFTP."""
        self.ftp.disconnect()
        return 1

    def do_exit(self, line):
        """Quit IrisFTP."""
        return self.do_quit(line)

    def do_get(self, line):
        """Download file from remote server.

        Syntax: get [--force] <remotefile> [localfile]
        """
        if not line: return

        params = line.split()

        force = False
        localfile = remotefile = None

        for para in params:
            if para[0:2] == "--":
                if para[2:] == "force": force = True
                else:
                    print "unknown paramter %s" % para
                    return
            else:
                if remotefile == None:
                    remotefile = para
                else:
                    localfile = para

        if remotefile == None:
            print "remotedir directory not specified"
            return

        try:
            self.ftp.get( remotefile, localfile, force )
        except IrisFTPException, e:
            print e

    def do_put(self, line):
        """Upload file to remote server.

        Syntax: put [--force] <localfile> [remotefile]
        """
        if not line: return

        params = line.split()

        force = False
        localfile = remotefile = None

        for para in params:
            if para[0:2] == "--":
                if para[2:] == "force": force = True
                else:
                    print "unknown paramter %s" % para
                    return
            else:
                if localfile == None:
                    localfile = para
                else:
                    remotefile = para

        if localfile == None:
            print "remotedir directory not specified"
            return

        try:
            self.ftp.put( localfile, remotefile, force )
        except IrisFTPException, e:
            print e

    def do_getdir(self, line):
        """Download directory to local server.

        Syntax: getdir [--force] [--recursive] <remotedir> [locadir]

        """
        if not line: return

        params = line.split()

        recursive = force = False
        remotedir = localdir = None

        for para in params:
            if para[0:2] == "--":
                if para[2:] == "force": force = True
                elif para[2:] == "recursive": recursive = True
                else:
                    print "unknown paramter %s" % para
                    return
            else:
                if remotedir == None:
                    remotedir = para
                else:
                    localdir = para

        if remotedir == None:
            print "remotedir directory not specified"
            return

        try:
            self.ftp.getdir( remotedir, localdir, force, recursive)
        except FileExistsException, e:
            print e

    def do_rmdir(self, line):
        """Delete directory from remote server.

        Syntax: rmdir <remotedir>

        """
        if not line: return

        try:
            self.ftp.rmdir(line.split()[0])
        except NoFileException, e:
            print e

    def do_putdir(self, line):
        """Upload directory to remote server.

        Syntax: getdir [--force] [--recursive] <localdir> [remotedir]

        """
        if not line: return

        params = line.split()

        recursive = force = False
        remotedir = localdir = None

        for para in params:
            if para[0:2] == "--":
                if para[2:] == "force": force = True
                elif para[2:] == "recursive": recursive = True
                else:
                    print "unknown paramter %s" % para
                    return
            else:
                if localdir == None:
                    localdir = para
                else:
                    remotedir = para

        if localdir == None:
            print "localdir directory not specified"
            return

        self.ftp.putdir( localdir, remotedir, force, recursive)

    def do_del(self, line):
        """Remove remote file."""
        if line:
            line = line.split()[0]
            try:
                self.ftp.delete(line)
            except NoFileException, e:
                print e

    def do_ls(self, line):
        """List remote directory content."""
        if line: line = line.split()[0]

        try:
            dirlist = self.ftp.dir(line)
        
            for file in dirlist:
                print file
        except IrisFTPException, e:
            print e

    def do_cd(self, line):
        """Change current remote directory."""
        if not line:
            return

        line = line.split()[0]

        try:
            self.ftp.cwd(line)
        except IrisFTPException, e:
            print e
    
    def do_pwd(self, line):
        """Print current remote directory."""
        print self.ftp.pwd()

    def do_l_del(self, line):
        """Remove local file."""
        if line:
            line = line.split()[0]
            os.unlink(line)

    def do_l_ls(self, line):
        """List local directory content."""
        if line: line = line.split()[0]
        else: line = "."

        print "\n".join(os.listdir(line))

    def do_l_cd(self, line):
        """Change current local directory."""
        if line: line = line.split()[0]
        else: line = "."

        os.chdir(line)

    def do_l_pwd(self, line):
        """Print current local directory."""
        print os.getcwd()

class FTPFileParser():
    filename = None     # filename of the scriptfile
    ftp = None          # ftp object
    verbose = False     # use verbose mode
    skipBlock = []      # stack to keep trace of if nesting
    lineCount = 0       # number of lines parsed

    def __init__(self, filename=None, sftp=None, verbose=False):
        self.filename = filename
        self.verbose=verbose
        if sftp:
            self.ftp = IrisSFTP(verbose=self.verbose)
        else:
            self.ftp = IrisFTP(verbose=self.verbose)

    def parse(self):
        if self.filename == None:
            return 1

        try:
            file = open(self.filename)
            
            self.parseHeader(file)

            if self.ftp.host and self.ftp.username and self.ftp.password: 
                try:
                    self.ftp.connect()
                except ConnectionException, e:
                    print e
                    return 1
            else:        
                raise ParserException, "Wrong header information"

            for line in file:
                self.lineCount = self.lineCount + 1
                self.parseLine(line.strip())

            self.ftp.disconnect()

        finally:
            file.close()

    def parseLine(self,line):
        """Parse a line, checking for further parsing."""
        if line == "" or line[0] == "#":
            return

        if line.strip()[0:5] == 'endif':
            try:
                del self.skipBlock[-1]
            except IndexError, e:
                sys.exit(1)
            return

        res = re.search(r'^if\s*\(([^)]*)\)', line)

        if res:
            if len(self.skipBlock) > 0 and self.skipBlock[-1]:
                # if we are in a false if block, append a skipblock to counter the coming endif
                self.skipBlock.append(True)
                return
            # evalute if test
            elif self.evaluate(res.group(1)):
                self.skipBlock.append(False)
                return
            else:
                self.skipBlock.append(True)
                return

        if len(self.skipBlock) > 0 and self.skipBlock[-1]:
            return


        self.parseCommand(line)

    def parseCommand(self, line):
        """Parse and execute a command line
        
        Arguments:
        line -- line to parse
        """
        
        line = line.split()

        command = line[0].strip()
        args = line[1:]

        try:
            if command == 'ls':
                command = 'dir'
            elif command == 'cd':
                command = 'cwd'
            elif command == 'print':
                print " ".join(args)
                return
            elif command == 'exit':
                if self.verbose: print "Exiting by script command, line %d" % self.lineCount
                self.ftp.disconnect()
                sys.exit(0)
        except AttributeError, e:
            raise CommandNotFound, "Command %s not found" % command


        res = None
        try:
            if len(args) > 0:
                arguments = []
                keyw = []
                for a in args:
                    if a[0:2] == "--":
                        keyw.append("%s='%s'" % (a[2:], a[2:]))
                    else:
                        arguments.append("'%s'" % a)
                arguments = arguments + keyw
                args = ",".join(arguments)
                command = "self.ftp.%s(%s)" % (command,args)
                res = eval(command)
            else:
                command = "self.ftp.%s()" % (command)
                res = eval(command)
            if res:
                print res
        except IrisFTPException, e:
            print "Exception %s: %s" % (e.__class__, e)

    def parseHeader(self, file):
        """Parse header information from file

        Arguments:
        file -- file object to parse
        """
        host = username = password = None
        for line in file:
            self.lineCount = self.lineCount + 1
            line = line.strip().split()

            if not line: 
                continue

            if line[0] == 'host:':
                host = line[1]
            elif line[0] == 'username:':
                username = line[1]
            elif line[0] == 'password:':
                password = line[1]
            else:
                raise ParserException, "Wrong header information"
            if host != None and username != None and password != None:
                break

        self.ftp.host = host
        self.ftp.username = username
        self.ftp.password = password

    def evaluate(self, expr):
        """Evalute an expression as true or false.
        
        Arguments:
        expr - expression to evalute
        """

        if expr.lower() == 'true': return True
        elif expr.lower() == 'false': return False

        if self.verbose: print expr

        expr = expr.split()

        res = False
        neg = False

        if expr[0].strip() == 'not':
            neg = True
            del expr[0]
       
        if expr[0].strip() == '-f':
            res = self.ftp.exists(expr[1].strip())
        elif expr[0].strip() == '-smax':
            try:
                size = self.ftp.size(expr[1].strip())
                res = (size < expr[2].strip())
            except NoFileException, e:
                res = False
        elif expr[0].strip() == '-smin':
            try:
                size = self.ftp.size(expr[1].strip())
                res = (size > expr[2].strip())
            except NoFileException, e:
                res = False
        elif expr[0].strip() == '-new':
            if not self.ftp.exists(expr[2].strip()): return False
            try:
                rem_time = self.ftp.time(expr[2].strip())
                loc_time = os.stat(expr[1].strip()).st_mtime
                res = (loc_time - rem_time) >= 0
            except NoFileException, e:
                res = False
        elif expr[0].strip() == '-old':
            if not self.ftp.exists(expr[2].strip()): return False
            try:
                rem_time = self.ftp.time(expr[2].strip())
                loc_time = os.stat(expr[1].strip()).st_mtime
                res = (loc_time - rem_time) <= 0
            except NoFileException, e:
                res = False

        if neg: return not res
        else: return res

def main(argv=None):
    """Main function."""

    userPrompt= "IrisFTP> "

    if argv is None:
        argv = sys.argv

    usage = "Usage: %prog [options]"
    parser = OptionParser(usage = usage)
    parser.add_option("--host", dest="host", metavar="HOST", \
            help="host adress of ftp server")
    parser.add_option("-u", "--username", dest="username", \
            metavar="USERNAME", help="username on the ftp server")
    parser.add_option("-p", "--password", dest="password", \
            metavar="PASSWORD", help="password on the ftp server")
    parser.add_option("-f", "--file", dest="file", \
            metavar="FILENAME", help="script file to execute, disables interactive prompt")
    parser.add_option("-s", "--sftp", dest="sftp", \
            action='store_true', help="set to use sftp (default=off)")
    parser.add_option("-v", "--verbose", dest="verbose", \
            action='store_true', help="set verbose mode (default=off)")

    (options, args) = parser.parse_args(argv)

    if len(args) != 1:
        parser.error("Wrong number of arguments");

    if options.sftp and not paramiko:
        parser.error("""Paramiko was not and is required for SFTP support
                Please install Paramiko or try without SFTP""")

    if options.file:
        if not os.path.isfile(options.file):
            parser.error("%s is not a file" % options.file)

        fileparser = FTPFileParser(options.file, options.sftp, options.verbose)
        fileparser.parse()
    else:
        try:
            cmd = FTPCommandLine(userPrompt, {'host':options.host, 'username':options.username, 'password': options.password}, options.sftp, options.verbose)

            cmd.cmdloop()
        except (KeyboardInterrupt, SystemExit):
            cmd.ftp.disconnect()

if __name__ == "__main__":
    sys.exit(main())
